package com.admin.aspect;

import com.admin.pojo.annotation.RepeatSubmit;
import com.admin.utils.CookieUtil;
import com.admin.utils.RedisUtilKey;
import com.admin.utils.ResponseResult;
import com.admin.utils.WebUtils;
import com.admin.utils.constants.CommonConstants;
import com.tiangong.framework.bo.Return;
import com.zmbsms.os.core.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * 定义一个切面类
 **/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspectVote {
    @Autowired
    private RedisUtilKey redisUtilKey;
 
    /**
     * 定义 @Pointcut注解表达式, 通过特定的规则来筛选连接点, 就是Pointcut，选中那几个你想要的方法
     * 在程序中主要体现为书写切入点表达式（通过通配、正则表达式）过滤出特定的一组 JointPoint连接点
     * 方式一：@annotation：当执行的方法上拥有指定的注解时生效（本博客采用这）
     * 方式二：execution：一般用于指定方法的执行
     */
    @Pointcut("@annotation(repeatSubmit)")
    public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
 
    }
 
    /**
     * 环绕通知, 围绕着方法执行
     * @param joinPoint
     * @param repeatSubmit
     * @return
     * @throws Throwable
     * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     * <p>
     * 方式一：单用 @Around("execution(* net.wnn.controller.*.*(..))")可以
     * 方式二：用@Pointcut和@Around联合注解也可以（本博客采用这个）
     * <p>
     * <p>
     * 两种方式
     * 方式一：加锁 固定时间内不能重复提交
     * <p>
     * 方式二：先请求获取token，这边再删除token,删除成功则是第一次提交
     */
    @Around("pointCutNoRepeatSubmit(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
        HttpServletRequest request = WebUtils.getRequest();
        log.info("request:{}",request);
        String accountNo = CookieUtil.getCookieUid(request);
        log.info("uuid:{}", accountNo);
        //用于记录成功或者失败
        Object res = false;
        //防重提交类型
        RepeatSubmit.Type type = repeatSubmit.limitType();
        log.info("环绕通知执行前");
        String requestToken = request.getHeader("csrfToken");
        RepeatSubmit.ReturnType returnType  =  repeatSubmit.returnType();
        String message = "";
        log.info("requestToken:{}",requestToken);
        if (type == RepeatSubmit.Type.TOKEN) {
            //方式二，令牌形式防重提交
            if (StringUtils.isBlank(requestToken)) {
                  log.error("请求重复提交,接口地址：{}",request.getRequestURL());
                    message ="未接收到csrfToken参数";
            }else{
                String key = String.format(CommonConstants.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
                /**
                 * 提交表单的token key
                 * 方式一：不用lua脚本获取再判断，之前是因为 key组成是 order:submit:accountNo, value是对应的token，所以需要先获取值，再判断
                 * 方式二：可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成
                 */
                log.info("防重提key:{}", key);
                res = redisUtilKey.get(key);
                redisUtilKey.del(key);
            }
        }
        if (res == null) {
            log.error("请求重复提交,接口地址：{}",request.getRequestURL());
            message ="请求重复提交";
        }
        if(StringUtils.isNotEmpty(message)){
            if(returnType == RepeatSubmit.ReturnType.RETURN ){
                return Return.valueOf(-1, message);
            }else {
                return ResponseResult.build(-1,message,null);
            }
        }
        return joinPoint.proceed();
    }
}